package start.spring.basic.util.common;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.FileHeader;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;

import java.io.OutputStream;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * 
 * ****************************************************************************
 * zip压缩工具类
 *
 * @author(作者)：xuyongyun	
 * @date(创建日期)：2019年1月9日
 ******************************************************************************
 */
public class ZipUtil {
	
	/** 
     * 使用给定密码解压指定的ZIP压缩文件到指定目录 
     * 
     * 如果指定目录不存在,可以自动创建,不合法的路径将导致异常被抛出 
     * 
     * @param zip 指定的ZIP压缩文件 
     * @param dest 解压目录 
     * @param passwd ZIP文件的密码 。需要解压密码时必须传入，否则传入null即可
     * 
     * @return  解压后文件名数组 
     * 
     * @throws ZipException 压缩文件有损坏或者解压缩失败抛出 
     */  
    @SuppressWarnings("unchecked")
	public static String[] unzip(File zipFile, String dest, String passwd) throws ZipException {  
        if(zipFile==null) {
        	 throw new ZipException("待解压的文件不能为空！");
        }else {
        	ZipFile zFile = new ZipFile(zipFile);
            zFile.setFileNameCharset("GBK");
            if(!zFile.isValidZipFile()) {
                throw new ZipException("压缩文件不合法,可能被损坏!");
            }
            
            File destDir = new File(dest);  
            if(destDir.isDirectory() && !destDir.exists()) {  
                destDir.mkdir();  
            }
            
            if(zFile.isEncrypted()) {
            	if(StringUtil.isEmpty(passwd)) {
            		throw new ZipException("文件已加密，需要解压密码，解压密码不能为空！");
            	}else {
            		zFile.setPassword(passwd.toCharArray());
            	}
            }
            zFile.extractAll(dest);
            
            List<String> extractedFileList = new ArrayList<String>();
            
            List<FileHeader> headerList = zFile.getFileHeaders();
            if(headerList!=null&&headerList.size()>0) {
            	for(FileHeader fileHeader:headerList) {
                    if(!fileHeader.isDirectory()) {
                        extractedFileList.add(fileHeader.getFileName());
                    }
                }
            }
            
            String[] extractedFiles = new String[extractedFileList.size()];
            extractedFileList.toArray(extractedFiles);
            return extractedFiles;
        }
    }
      
    /** 
     * 使用给定密码压缩指定文件或文件夹到指定位置
     * 
     * @param src 要压缩的文件或文件夹路径 
     * @param destFile 压缩文件的绝对路径,包含文件名如:C:/a/b.zip
     * @param passwd 压缩使用的密码 
     * 
     * @return 最终的压缩文件存放的绝对路径,如果为null则说明压缩失败
     * 
     * @throws ZipException 未指定需要压缩的文件时抛出
     * @throws IOException 无法创建压缩文件时抛出
     *  
     */  
    public static String zip(File srcFile, String destFile, String passwd) throws ZipException, IOException {  
    	
    	if(srcFile==null) {
    		throw new ZipException("请指定要压缩的文件！");
    	}
    	
    	if(StringUtil.isEmpty(destFile)) {
    		throw new ZipException("请指压缩文件的绝对路径！");
    	}
    	
    	if(destFile.indexOf(".zip")<=0) {
    		throw new ZipException("压缩文件的后缀不正确！");
    	}
        
        File tmpDestFile = new File(destFile);
        if(!tmpDestFile.exists()) {
        	tmpDestFile.createNewFile();
        }
        if(!tmpDestFile.isFile()) {
        	tmpDestFile.delete();
        	throw new ZipException("传入的压缩文件路径不对,请检查");
        }
        
        ZipParameters parameters = new ZipParameters();  
        parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
        parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);
        if(!StringUtil.isEmpty(passwd)) { 
            parameters.setEncryptFiles(true);  
            parameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_STANDARD);
            parameters.setPassword(passwd.toCharArray());
        }
        
        try{
            ZipFile zipFile = new ZipFile(destFile);
            if(srcFile.isDirectory()) {
                zipFile.addFolder(srcFile, parameters);
            }else {
                zipFile.addFile(srcFile, parameters);
            }
        }catch (ZipException e) {
        	throw e;
        }
        return destFile;
    }
    
    /**
     * 分卷压缩
     *	
     * @param srcFiles 要压缩的文件绝对路径列表
     * @param destFile 要压缩的zip文件名
     * @param passwd   压缩密码
     * @param fileSize 分卷大小，单位byte
     * @return
     * @throws ZipException
     * @since           1.0
     */
    public static String zip(List<String> srcFiles,String destFile, String passwd,long fileSize) throws ZipException{
    	String zipFiles = null;
    	File tmpFile = new File(destFile);
    	if(tmpFile.exists()){
    		tmpFile.delete();
    	}
    	ZipFile zipFile = new ZipFile(destFile);
    	ArrayList<File> filesToAdd = new ArrayList<File>();
    	if(srcFiles!=null&&srcFiles.size()>0){
    		int fileCount = srcFiles.size();
    		for(int i=0;i<fileCount;i++){
    			filesToAdd.add(new File(srcFiles.get(i)));
    		}
    		
            ZipParameters parameters = new ZipParameters();
            if(!StringUtil.isEmpty(passwd)) { 
                parameters.setEncryptFiles(true);  
                parameters.setEncryptionMethod(Zip4jConstants.ENC_METHOD_STANDARD);
                parameters.setPassword(passwd.toCharArray());
            }
            parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);

            parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL);
            
            //未指定则设置为512KB
            if(fileSize==0){
            	fileSize = 65536;
            }
            zipFile.createZipFile(filesToAdd, parameters, true, fileSize);
    	
            @SuppressWarnings("unchecked")
			ArrayList<String> zipList = zipFile.getSplitZipFiles();
            String[] zipArray = new String[zipList.size()];
            zipList.toArray(zipArray);
            zipFiles = Arrays.toString(zipArray);
            
            int length = zipFiles.length();
            zipFiles = zipFiles.substring(1, length - 1);    	
        }
    	return zipFiles;
    }

    /**
     * 替换某个 item,
     * @param zipInputStream zip文件的zip输入流
     * @param zipOutputStream 输出的zip输出流
     * @param itemName 要替换的 item 名称
     * @param itemInputStream 要替换的 item 的内容输入流
	 * @author lrc 20200730
     */
    public static void replaceItem(ZipInputStream zipInputStream,
                                   ZipOutputStream zipOutputStream,
                                   String itemName,
                                   InputStream itemInputStream){
        if(null == zipInputStream){
        	return;
        }
        if(null == zipOutputStream){
        	return;
        }
        if(null == itemName){
        	return;
        }
        if(null == itemInputStream){
        	return;
        }
        
        ZipEntry entryIn;
        try {
            while ((entryIn = zipInputStream.getNextEntry()) != null) {
                String entryName =  entryIn.getName();
                ZipEntry entryOut = new ZipEntry(entryName);
                // 只使用 name
                zipOutputStream.putNextEntry(entryOut);
                // 缓冲区
                byte [] buf = new byte[8*1024];
                int len;

                if(entryName.equals(itemName)){
                    // 使用替换流
                    while((len = (itemInputStream.read(buf))) > 0) {
                        zipOutputStream.write(buf, 0, len);
                    }
                } else {
                    // 输出普通Zip流
                    while((len = (zipInputStream.read(buf))) > 0) {
                        zipOutputStream.write(buf, 0, len);
                    }
                }
                // 关闭此 entry
                zipOutputStream.closeEntry();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(itemInputStream);
            close(zipInputStream);
            close(zipOutputStream);
        }
    }
    
    /**
     * 包装输入流
     */
    public static ZipInputStream wrapZipInputStream(InputStream inputStream){
        ZipInputStream zipInputStream = new ZipInputStream(inputStream);
        return zipInputStream;
    }
    
    /**
     * 包装输出流
     */
    public static ZipOutputStream wrapZipOutputStream(OutputStream outputStream){
        ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream);
        return zipOutputStream;
    }
    
	/**
	 * 关闭输入流
	 * @param inputStream
	 */
    private static void close(InputStream inputStream){
        if (null != inputStream){
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 关闭输出流
     * @param outputStream
     */
    private static void close(OutputStream outputStream){
        if (null != outputStream){
            try {
                outputStream.flush();
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
